#!/sw/bin/perl

use strict;
use warnings;

use threads;
use threads::shared;

use Tk;
use Tk::ROText;
use Tk::Dialog;
use Tk::HList;
use Tk::NoteBook;

use IO::Socket;
use IO::File;

use File::Path;
use Cwd;
use YAML;


#GUI init functions
sub initialize_menu;
sub initialize_frame;
sub initialize_list;
sub initialize_input;
sub initialize_dialog;
sub initialize_display;

# Other functions - To be sorted and commented
sub parse_input;
sub tab_complete;
sub server_connect;
sub server_listen;
sub channel_connect;
sub channel_watch;
sub raw_socket_parser;
sub channel_broadcast;

#thread hash
my %server_threads;

#mutex hashses
my %send_mutex;
my %update_mutex;
my %update_string;

#server structure hashses
my %server;
my %server_message;

#updater function hash
my %updater;

#Channel data hashes


#GUI hashes
my %menu;
my %frame;
my %tab;
my %list;
my %dialog;
my %text;

my $exec_path = cwd();
my $logs_path = $exec_path . "/../logs";
my $conf_path = $exec_path . "/../data";

my @server_refs = YAML::LoadFile( $conf_path . "/config.yaml" );

my $hlist;
my $book;
my $input;
my $menu;
my $window;

#shared data
share( %send_mutex );
share( %updater );
share( %update_mutex );
share( %update_string );

$window = new MainWindow( -title => 'msqirc' );
$menu = $window->Menu;

$window->configure( -menu => $menu );

&initialize_menu;
&initialize_dialog;
&initialize_frame;
&initialize_list;
&initialize_display;
&initialize_input;

$input->focus;
$window->repeat(300, \&update_gui) ;

for( @server_refs ) {
  &server_connect( $_ );
  my $host = $_->{ host };
  print "$host\n\n";
  for my $x ( @{ $_->{ channels }} ) {
    &channel_connect( $_, $x );
  }
  $server_threads{ $host } = threads->create( \&server_listen, $_ );
}

MainLoop;



#GUI Functions
##Initializers
sub initialize_dialog {
	
	
	$dialog{ about } = $window->Dialog( 
                                     -title => 'about', 
                                     -text => "Version 0.01",
                                     -bitmap => 'info',
                                     -buttons => ['Dismiss'],
                                    );
	$dialog{ about }->configure( -wraplength => '6i' );
	
	$dialog{ usage } = $window->Dialog( 
                                     -title => 'Usage',
                                     -text => "Not useable right now." ,
                                     -buttons => ['Dismiss'],
                                    );
}

sub initialize_menu {
	
	$menu{ file } = $menu->cascade( 
                                 -label => '~File',
                                 -tearoff => 0,
                                );
	$menu{ file }->command(
                         -label => 'Quit',
                         -command => sub { exit },
                         -underline => 0,
                        );
	
	$menu{ edit } = $menu->cascade(
                                 -label => '~Edit',
                                 -tearoff => 0,
                                );
	
	$menu{ help } = $menu-> cascade(
                                  -label => '~Help',
                                  -tearoff => 0,
                                 );
	$menu{ help }->command(
                         -label => 'About',
                         -command => sub { print 'foo\n' },
                         -underline => 0
                        );
}

sub initialize_frame {
	$frame{ super } = $window->Frame()->pack(
                                           -side   => 'top',
                                           -fill   => 'both',
                                           -expand => 1
                                          );
	$frame{ left } = $frame{ super }->Frame()->pack(
                                                  -side   => 'left',
                                                  -fill   => 'y',
                                                  -expand => 1
                                                 );
	$frame{ right } = $frame{ super }->pack(
                                          -side   => 'right',
                                          -fill   => 'both'
                                         );
}

sub initialize_list {
	$hlist = $frame{ left }->Scrolled(
                                    'HList',
                                    -width       => 20,
                                    -itemtype    => 'text',
                                    -separator   => '-',
                                    -selectmode  => 'single',
                                    -scrollbars  => 'osoe',
                                    -command     => sub { $book->raise( shift( @{ $hlist->info( 'selection' )})) }
                                   )->pack(-side   =>'top',
                                           -fill   => 'y',
                                           -expand => 1,
                                          );
	&hlist_add( 'diagnostic' ); 
}

sub initialize_display {
	
	$book = $frame{ right }->NoteBook()->pack;
	&tab_construct( 'diagnostic' );
	
	
}

sub initialize_input {
	$input = $frame{ right }->Entry()->pack(
                                          -side   => 'left',
                                          -fill   => 'both',
                                          -expand => 1,
                                          -pady   => 2
                                         );
	$input->bind("<Return>", \&parse_input );
	$input->bind("<Control-Return>", \&tab_complete );
	$input->bind("<Tab>", \&tab_complete );
	
}


##Updaters

sub hlist_add {
	
	my $input = shift;
	my ($host, $chan) = split( /-/, $input, 2);
  
  warn "No host provided\n" if not $host;
  $chan = "NO_CHANNEL" if not $chan;
  
	my $text = $host;
	
	
	if ( $chan ne "NO_CHANNEL" ) {
		$host = "$host-$chan";
		$text = $chan;
	}
  
  print "\n\t$host--::--$chan--::--$text\n";
	$hlist->add( $host, -text => $text );
}

sub hlist_remove {
	my $target = shift || warn "No target provided\n";
	
	$hlist->delete( 'entry', $target)
}

sub tab_construct {
	
	my $name = shift || warn "No name provided\n", return;
	
  $name =~ m/([^-]+)-?(.+)?/;
  my $label = $2 ? $2 : $name;
  
  
	$tab{ $name } = $book->add( $name, -label => $label );
	$text{ $name } = $tab{ $name }->Scrolled( "ROText",
                                            -width => 80,
                                            -height => 30,
                                            -wrap => 'word',
                                            -bg => 'white',
                                            -relief => 'sunken',
                                            -scrollbars => 'oe' 
                                          )->pack(-side   => 'top',
                                                  -fill   => 'both',
                                                  -expand => 1);
	
}

sub tab_destruct {
  
  my $target = shift || warn "No target provided", return;
  
  $book->delete( $target );
}

sub tab_write {
	
	my ($target, $message) = @_;
	#  my $message = shift;
	#  my $name = $book->raised;
	my $text = $text{ $target } || warn "Incorrect data\n", return;
  
  print "\t$target-*-$message\n";
  
	$text->insert( 'end', $message );
  $text->see( 'end' );
}


##GUI Update Functions
sub update_gui {
  for my $key ( keys %update_mutex ) {
    update_tab( $key );
  }
}

sub update_tab {
	
	#pull in target or cancel update;
	my $target = shift || return;
	
  
	#wait for lock to open
	return if( $update_mutex{ $target } == 1 || 
             $send_mutex{ $target } == 0 ||
             !$update_string{ $target } );
	
  print "\t$target\n\t$update_mutex{ $target }\n\t$send_mutex{ $target }\n\t$update_string{ $target }\n";

  
	#lock mutex
	$update_mutex{ $target } = 1;
	
	#run update
	&tab_write( $target, $update_string{ $target } );
	
	#unlock mutexs
	$update_mutex{ $target } = 0;
	$send_mutex{ $target } = 0;
  $update_string{ $target } = "";
}


##sever functions

##Server Functions
sub server_connect {
  
  my $ref       = shift               || warn "No data passed to \&server_connect\n";
  my $host      = $ref->{ host }      || warn "No host name provided for current server\n";
  my $port      = $ref->{ port }      || 6667;
  my $nick      = $ref->{ nick }      || getlogin;
  my $ident     = $ref->{ ident }     || "msqirc/usr";
  my $real_name = $ref->{ real_name } || getlogin;
  
  
  $ref->{ socket } = new IO::Socket::INET (
                                           PeerAddr => $host,
                                           PeerPort => $port,
                                           Proto    => 'tcp'
                                          ) or warn "Could not create socket to $host\n";
  
  my $socket = $ref->{ socket };
  
  print $socket "NICK $nick \r\n";
  print $socket ":$nick USER $ident $host bla :$real_name\r\n";
  
  &hlist_add( $host );
  &tab_construct( $host );
  
  $send_mutex{ $host } = 0;
  $update_mutex{ $host } = 0;
  $update_string{ $host } = "";
  $server{ $host } = {
                      host   => $ref->{ host },
                      socket => $ref->{ socket },
                      nick   => $ref->{ nick }
                     };
  $server_message{ $host } = "";
}

sub server_listen {
  
  my $ref = shift             || warn "No data passed to \&server_listen\n";
  my $host = $ref->{ host }   || warn "No host name provided for this server\n";
  my $sock = $ref->{ socket } || warn "Not connected to server $host\n";
  
  while ( my $line = <$sock> ) {
    $line =~ s/^\s*(\S*(?:\s+\S+)*)\s*$/$1/g;
    if ( $line =~ m/^PING(.+)/ ) {
      print $sock "PONG $1\r\n";
    }
    elsif ( $line =~ m/^:([^!]+![^@]+\@[^ ]+|[^ ]+) ([A-Z]+|\d+) (.+)$/ ) {
      &event_handler( $ref, $1, $2, $3 );
    } 
    else {
      warn "Unknown input from server: $host\n$line\n";
    }
  }
}

sub server_talk {
  
  my $ref       = shift             || warn "No data passed to \&server_connect\n";
  my $host      = $ref->{ host }    || warn "No host name provided for current server\n";
  my $port      = $ref->{ port }    || 6667;
  my $nick      = $ref->{ nick }    || getlogin;
  my $ident     = $ref->{ ident }   || "msqirc/usr";
  my $sock      = $ref->{ socket }  || warn "No socket for \&server_talk\n";
  my $message   = shift;

  while ( "TRUE" ) {
    if( $server_message{ $host } ) {
      print $sock ":$nick $message";     
      $server_message{ $host } = "";
    }
    else {
      select(undef, undef, undef, 0.30);
    }
  }
}

##channel functions
sub channel_connect {
  
  my $ref      = shift             || warn "No data passed to \&channel_connect\n";
  my $host     = $ref->{ host }    || warn "No host name provided for this channel\n";
  my $sock     = $ref->{ socket }  || warn "Not connected to server $host\n";
  my $nick     = $ref->{ nick }    || warn "You should really never see this\n";
  my $channel  = shift             || warn "No channel provided to \&channel_connect\n";
  
  print $sock  ":$nick JOIN $channel\r\n";
  
  &hlist_add( "$host-$channel" );
  &tab_construct( "$host-$channel" );
  
  $send_mutex{ "$host-$channel" } = 0;
  $update_mutex{ "$host-$channel" } = 0;
  $update_string{ "$host-$channel" } = "";
}


##logging functions
sub generate_logfile {
  
  my $ref = shift           || warn "No data passed - creating dummy log file\n";
  my $channel = shift       || "SERVER_LOG";
  my $host = $ref->{ host } || warn "No host for this log\n";
  
  my $file_location = $logs_path;
  
  $file_location . "/" . $host    if $host;
  $file_location . "/" . $channel if $channel;
  
  mkpath( $file_location );
  
  my ( $s, $m, $h, $day, $mon, $year, $w, $y, $i) = localtime( time );  
  my $file_name = ($year + 1900) . "-$mon-$day";
  
  return new IO::File( "$file_location/$file_name", "a" );
}

sub event_handler {
  my ($ref, $who, $command, $message) = @_;
  
  my ($nick, $ident, $host_mask) = split( /!|@/, $who, 3 );
  
  my $host = $ref->{ host } || warn "No data passed\n";
  
  my ($seconds, $minuets, $hours) = localtime(time);
  $hours   = "0$hours"   if length $hours   == 1;
  $minuets = "0$minuets" if length $minuets == 1;
  
  #tagAdd('bold', '1.0', 'end');
  
  if ( $command =~ m/PRIVMSG/ ) {
    $message =~ s/\x{1}//g;
    $message =~ m/([^ :]+) :(ACTION )?(.+)/;
    
    my $write_string = $2 ? "$hours:$minuets !" : "$hours:$minuets ";
    &send_message_to_gui( "$host-$1", "$write_string $nick<b>\$</b> $3\n" );
  }
  elsif( $command =~ m/JOIN/ ) {
    $message =~ m/^:(.+)/;
    &send_message_to_gui( "$host-$1", "$hours:$minuets    #   $nick join - [ $1 ]\n" );
  }
  elsif( $command =~ m/PART/ ) {
    $message =~ m/([^: ]+) ?:?(.+)?/;
    &send_message_to_gui( "$host-$1", "$hours:$minuets    #   $nick part - [ $2 ]\n" );
  }
  elsif( $command =~ m/QUIT/ ) {
    $message =~ m/:QUIT: ?(.+)?/;
    &send_message_to_gui( "$host-$1", "$hours:$minuets    #   $nick quit - [ $2 ]\n" );
  }
  elsif( $command =~ m/NICK/ ) {
    $message =~ m/:(.+)/;
    &channel_broadcast( $ref, "$hours:$minuets    #   $nick is now known as $1\n" );
  }
  elsif( $command =~ m/MODE/ ) {
    my ($channel, $change, $who) = split( / /, $message, 3 );
    
    my $output = "$hours:$minuets    #   mode/$channel \[$change ";
    $output = $who ? "$output $who\]" : "$output\]";
    $output = $output . " by $nick\n";
    
    &send_message_to_gui( "$host-$channel", $output );
  }
  elsif( $command == 353 ) {
    $message =~ m/[^&#+!]+((&|#|\+|!)[^ ,]+) :(.+)/;
    send_message_to_gui( "$host-$1", "NAMES: $3\n" );
  }
  else {
    $who =~ m/([^.]+)\.([^.]+)\.([^.]+)/;
    if( $host =~ /([^.]+)\.$2\.$3/ ) {
      $message =~ m/[^:]+:(.+)/;
      &send_message_to_gui( $host, "$hours:$minuets - $1\n" );
    }
  }
  print "$who-*-*-$command-*-*-$message\n";
  &send_message_to_gui('diagnostic', "$who-*-*-$command-*-*-$message\n" );
}

sub send_message_to_gui {
  my $target = shift  || warn "No target\n",  return;
  my $message = shift || warn "No message\n", return;
  
  while( $update_mutex{ $target } || $send_mutex{ $target } || $update_string{ $target } ) {
    select(undef, undef, undef, 0.30);
  }
  
  $send_mutex{ $target } = 1;
  $update_mutex{ $target } = 1;
  $update_string{ $target } = $message;
  $update_mutex{ $target } = 0;
}

sub parse_input {
  my $message = $input->get;
	my $name = $book->raised;
  
  my ($host, $channel) = split( /-/, $name, 2);
  
  my $ref = $server_threads{ $host };
  
  $input->delete( 0, length $message );
  if ( $message =~ m|^/| ) {
    print "$message\n";
  }
  
  my ($seconds, $minuets, $hours) = localtime(time);
  $hours   = "0$hours"   if length $hours   == 1;
  $minuets = "0$minuets" if length $minuets == 1;
  
  &send_message_to_gui( $name, "$hours:$minuets  msq_alpha\$ $message\n" );
  &send_message_to_socket( $server{ $host }, $channel, $message );
}

sub send_message_to_socket {
  my $ref      = shift             || warn "No data passed to \&send_message_to_socket";
  my $host     = $ref->{ host }    || warn "No host name provided for this channel\n";
  my $sock     = $ref->{ socket }  || warn "Not connected to server $host\n";
  my $nick     = $ref->{ nick }    || warn "You should really never see this\n";
  my $channel  = shift             || warn "No channel provided to \&send_message_to_socket";
  my $message  = shift             || warn "No message provided to \&send_message_to_socket";
  
  print $sock ":$nick PRIVMSG $channel :$message\r\n";
}

sub tab_complete {
  print "to be completed";
}


sub channel_broadcast {  
  my $ref = shift                       || warn "No data passed\n";  
  my $host = $ref->{ host }             || warn "No host provided\n";
  my $message = shift                   || warn "No message passed\n", return;
    
  for my $channel ( @{ $ref->{ channels }} ) {
    &send_message_to_gui( "$host-$channel", $message );
  }
}
